package com.gaj.util;

import org.apache.commons.net.ftp.FTPClient;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.zip.ZipInputStream;

/**
 * SM4算法CBC模式ZIP文件加解密工具类
 * <p>
 * 职责：
 * 1. SM4算法加密解密
 * 2. ZIP文件流处理
 * 3. FTP服务器文件上传
 * 4. 字节数组转换
 * 5. 加密参数管理
 *
 * 使用场景：
 * 政法智能办案平台ZIP文件的SM4加密解密处理
 *
 * 加密参数：
 * - 算法：SM4/CBC/PKCS7Padding
 * - 密钥：64425A2C2BB32CE5（16位十六进制）
 * - 初始化向量：905EF8C3C12C3087（16位十六进制）
 *
 * @author 系统
 * @version 1.0
 */
public class SM4ZipUtils {

    // ==================== 加密算法常量 ====================

    /** SM4算法名称 */
    private static final String ALGORITHM_NAME = "SM4";

    /** SM4算法模式：CBC模式配合PKCS7填充 */
    private static final String ALGORITHM_MODE_PADDING = "SM4/CBC/PKCS7Padding";

    /** 16位十六进制密钥 */
    private static final String SECRET_KEY = "64425A2C2BB32CE5";

    /** 16位十六进制初始化向量 */
    private static final String IV = "905EF8C3C12C3087";

    // ==================== 静态初始化 ====================

    /**
     * 静态初始化块
     * 加载BouncyCastleProvider以支持SM4算法
     */
    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
            //System.out.println("BouncyCastleProvider已加载，支持SM4算法");
        } else {
            System.out.println("BouncyCastleProvider已存在");
        }
    }

    // ==================== 加密方法 ====================

    /**
     * 加密ZIP文件
     *
     * 加密流程：
     * 1. 转换密钥和IV为字节数组
     * 2. 初始化SM4加密器
     * 3. 读取原ZIP文件并加密
     * 4. 写入加密后的文件
     *
     * @param srcZipPath 原ZIP文件路径（如：D:/test/source.zip）
     * @param destZipPath 加密后ZIP文件路径（如：D:/test/source_SM4_EN.zip）
     * @throws Exception 加密过程中的异常（文件未找到、算法错误等）
     */
    public static void encryptZip(String srcZipPath, String destZipPath) throws Exception {
        //System.out.println("开始加密ZIP文件: " + srcZipPath + " -> " + destZipPath);

        // 1. 转换密钥和IV为字节数组（十六进制字符串转字节）
        byte[] keyBytes = toSm4KeyOrIvBytes(SECRET_KEY);
        byte[] ivBytes = toSm4KeyOrIvBytes(IV);
        ////System.out.println("密钥和IV转换完成");

        // 2. 初始化SM4加密器
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, BouncyCastleProvider.PROVIDER_NAME);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM_NAME);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        //System.out.println("SM4加密器初始化完成");

        // 3. 读取原ZIP文件，通过加密器处理后写入目标文件
        try (InputStream in = new FileInputStream(srcZipPath);
             OutputStream out = new FileOutputStream(destZipPath);
             CipherOutputStream cipherOut = new CipherOutputStream(out, cipher)) {

            byte[] buffer = new byte[1024 * 4]; // 4KB缓冲区
            int len;
            long totalBytes = 0;

            while ((len = in.read(buffer)) != -1) {
                cipherOut.write(buffer, 0, len);
                totalBytes += len;
            }
            cipherOut.flush();

            //System.out.println("ZIP文件加密完成，总字节数: " + totalBytes);
        }
    }

    // ==================== 解密方法 ====================

    /**
     * 解密ZIP文件到输出流
     *
     * 解密流程：
     * 1. 创建字节数组输出流
     * 2. 调用通用解密方法
     * 3. 返回输出流
     *
     * @param inputStream 加密后的ZIP文件输入流
     * @return 解密后的输出流
     * @throws Exception 解密过程中的异常
     */
    public static ByteArrayOutputStream decryptZipToOutputStream(InputStream inputStream) throws Exception {
        //System.out.println("开始解密ZIP文件到输出流");

        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            // 调用通用的解密方法
            decryptZipToOutputStream(inputStream, out);
            System.out.println("ZIP文件解密到输出流完成");
            return out;
        }
    }

    /**
     * 通用解密方法：将加密的ZIP文件解密到指定的输出流
     * <p>
     * 解密流程：
     * 1. 转换密钥和IV为字节数组
     * 2. 初始化SM4解密器
     * 3. 读取加密数据并解密
     * 4. 输出解密后的数据
     *
     * @param inputStream 加密后的ZIP文件输入流
     * @param outputStream 解密后的数据输出流
     * @throws Exception 解密过程中的异常
     */
    private static void decryptZipToOutputStream(InputStream inputStream, OutputStream outputStream) throws Exception {
        //System.out.println("开始解密ZIP文件到输出流");

        // 1. 转换密钥和IV为字节数组（与加密时一致）
        byte[] keyBytes = toSm4KeyOrIvBytes(SECRET_KEY);
        byte[] ivBytes = toSm4KeyOrIvBytes(IV);
        //System.out.println("密钥和IV转换完成");

        // 2. 初始化SM4解密器
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, BouncyCastleProvider.PROVIDER_NAME);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM_NAME);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        //System.out.println("SM4解密器初始化完成");

        // 3. 读取加密后的ZIP文件，通过解密器处理后输出到指定流
        try (CipherInputStream cipherIn = new CipherInputStream(inputStream, cipher)) {
            byte[] buffer = new byte[1024 * 4]; // 4KB缓冲区
            int len;
            long totalBytes = 0;

            // 分块解密：每次读取加密数据的一部分，解密后写入输出流
            while ((len = cipherIn.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
                totalBytes += len;
            }
            outputStream.flush();

            //System.out.println("ZIP文件解密完成，总字节数: " + totalBytes);
        }
    }

    /**
     * 解密ZIP文件并保存到本地
     *
     * 解密流程：
     * 1. 创建文件输入流
     * 2. 调用通用解密方法
     *
     * @param encryptedZipPath 加密后的ZIP文件路径（如：D:/test/source_SM4_EN.zip）
     * @param destZipPath 解密后恢复的ZIP文件路径（如：D:/test/source_SM4_DE.zip）
     * @throws Exception 解密过程中的异常（密钥错误、文件损坏、算法错误等）
     */
    public static void decryptZipToLocal(String encryptedZipPath, String destZipPath) throws Exception {
        //System.out.println("开始解密ZIP文件到本地: " + encryptedZipPath + " -> " + destZipPath);
        decryptZipToLocal(Files.newInputStream(Paths.get(encryptedZipPath)), destZipPath);
    }

    /**
     * 解密ZIP文件并保存到本地
     *
     * 解密流程：
     * 1. 创建本地文件输出流
     * 2. 调用通用的解密方法
     *
     * @param inputStream 加密后的ZIP文件输入流
     * @param destZipPath 解密后恢复的ZIP文件路径（如：D:/test/source_SM4_DE.zip）
     * @throws Exception 解密过程中的异常（密钥错误、文件损坏、算法错误等）
     */
    public static void decryptZipToLocal(InputStream inputStream, String destZipPath) throws Exception {
        //System.out.println("开始解密ZIP文件到本地路径: " + destZipPath);

        // 创建本地文件输出流
        try (OutputStream out = Files.newOutputStream(Paths.get(destZipPath))) {
            // 调用通用的解密方法
            decryptZipToOutputStream(inputStream, out);
            //System.out.println("ZIP文件解密到本地完成: " + destZipPath);
        }
    }


    // ==================== 字节数组处理方法 ====================

    /**
     * 解密ZIP文件到字节数组
     *
     * 解密流程：
     * 1. 创建字节数组输出流
     * 2. 调用通用解密方法
     * 3. 返回解密后的字节数组
     *
     * @param inputStream 加密后的ZIP文件输入流
     * @return 解密后的字节数组
     * @throws Exception 解密过程中的异常
     */
    public static byte[] decryptZipToBytes(InputStream inputStream) throws Exception {
        //System.out.println("开始解密ZIP文件到字节数组");

        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            // 调用通用的解密方法
            decryptZipToOutputStream(inputStream, out);
            byte[] result = out.toByteArray();
            //System.out.println("ZIP文件解密到字节数组完成，大小: " + result.length + " 字节");
            return result;
        }
    }

    // ==================== 工具方法 ====================

    /**
     * 十六进制字符串转字节数组
     * <p>
     * 转换逻辑：
     * 1. 检查输入是否为空
     * 2. 计算字节数组长度
     * 3. 每两个十六进制字符转换为一个字节
     *
     * @param hexStr 十六进制字符串
     * @return 对应的字节数组
     */
    private static byte[] hexStringToBytes(String hexStr) {
        if (hexStr == null || hexStr.isEmpty()) {
            return new byte[0];
        }

        int len = hexStr.length();
        byte[] bytes = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            bytes[i / 2] = (byte) ((Character.digit(hexStr.charAt(i), 16) << 4)
                    + Character.digit(hexStr.charAt(i + 1), 16));
        }

        return bytes;
    }

    /**
     * 将密钥/IV字符串转换为SM4所需的16字节
     *
     * 转换逻辑：
     * 1. 检查输入是否为空
     * 2. 如果是32位十六进制字符串，按hex解码为16字节
     * 3. 否则按字面字符串以UTF-8取bytes，需正好16字符
     *
     * @param value 密钥或IV字符串
     * @return 16字节的字节数组
     * @throws IllegalArgumentException 输入格式不正确时抛出异常
     */
    private static byte[] toSm4KeyOrIvBytes(String value) {
        if (value == null) {
            throw new IllegalArgumentException("Key/IV不能为空");
        }

        String trimmed = value.trim();
        //System.out.println("转换密钥/IV: " + trimmed);

        // 1. 32位全hex → 解码成16字节
        if (trimmed.length() == 32 && trimmed.matches("[0-9a-fA-F]+")) {
            byte[] bytes = hexStringToBytes(trimmed);
            if (bytes.length != 16) {
                throw new IllegalArgumentException("hex解码后长度不是16字节");
            }
            System.out.println("十六进制解码成功，长度: " + bytes.length + " 字节");
            return bytes;
        }

        // 2. 否则按字面字符串，要求正好16字符
        if (trimmed.length() != 16) {
            throw new IllegalArgumentException("Key/IV长度必须为16字符，或提供32位hex：" + trimmed.length());
        }

        byte[] bytes = trimmed.getBytes(StandardCharsets.UTF_8);
        if (bytes.length != 16) {
            throw new IllegalArgumentException("按UTF-8编码后的Key/IV必须为16字节");
        }

        //System.out.println("UTF-8编码成功，长度: " + bytes.length + " 字节");
        return bytes;
    }

    // ==================== 测试方法 ====================

    /**
     * 测试方法（直接运行即可验证）
     *
     * 测试流程：
     * 1. 配置测试文件路径
     * 2. 执行加密测试（可选）
     * 3. 执行解密测试
     *
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        try {
            // 1. 配置测试文件路径（请替换为你的实际文件路径）
            @SuppressWarnings("unused")
            String originalZip = "D:\\Programs\\FTP共享文件夹\\bjcm\\fy\\receive\\data\\1301E\\1301E.zip"; // 要加密的原始ZIP文件
            String encryptedZip = "D:\\Programs\\FTP共享文件夹\\bjcm\\fy\\receive\\data\\1301E\\1301_1301E_21_11022823030000_7ececeeb3d7448dba41bbe43457fb750_350DD490BD359D227AE810D861B33449.zip"; // 加密后输出的ZIP文件
            String decryptedZip = "D:\\Programs\\FTP共享文件夹\\bjcm\\fy\\receive\\data\\1301E\\1301E.zip"; // 解密后恢复的ZIP文件

            // 2. 执行加密测试（可选）
            // System.out.println("开始加密ZIP文件...");
            // encryptZip(originalZip, encryptedZip);
            // System.out.println("加密完成！加密文件路径：" + encryptedZip);

            // 3. 执行解密测试
            System.out.println("开始解密ZIP文件到本地...");
            decryptZipToLocal(encryptedZip, decryptedZip);
            System.out.println("解密完成！解密文件路径：" + decryptedZip);

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("加解密测试失败：" + e.getMessage());
        }
    }
}
